using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Perovich.GameObjects.Interfaces;
using Microsoft.Xna.Framework.Content;

namespace Perovich.GameObjects
{
    /// <summary>
    ///   Lightweight variant DrawableGameComponent that doesn't reference the Game class
    /// </summary>
    /// <remarks>
    ///   <para>
    ///     This is a lightweight version of DrawableGameComponent that can be used
    ///     without requiring a Game class to be present. Useful to get all the
    ///     advantages of the XNA GameServices architecture even when you have
    ///     initialized and manage the graphics device yourself.
    ///   </para>
    ///   <para>
    ///     To work, this class requires to things: A GameServices collection and
    ///     an entry for the IGraphicsDeviceService in a class implementing <see cref="Perovich.GameObjects.Interfaces.ICanHoldComponents"/> You can easily implement this
    ///     interface yourself for any custom graphics device manager.
    ///   </para>
    ///   <para>
    ///     The name of this class is the same as 'DrawableGameComponent' minus the
    ///     'Game' part as the Game reference is what this class removes from its namesake.
    ///   </para>
    /// </remarks>
    public abstract class DrawableComponent : Component, IDrawableExt, IDisposable
    {

        /// <summary>Triggered when the value of the <see cref="DrawableComponent.DrawOrder"/> property is changed.</summary>
        public event EventHandler<EventArgs> DrawOrderChanged;

        /// <summary>Triggered when the value of the <see cref="DrawableComponent.Visible"/> property is changed.</summary>
        public event EventHandler<EventArgs> VisibleChanged;

        /// <summary>Initializes a new <see cref="DrawableComponent"/>.</summary>
        /// <param name="parent">
        ///   <see cref="Perovich.GameObjects.Interfaces.ICanHoldComponents"/> instance that this <see cref="DrawableComponent"/> will be constructed for.
        /// </param>
        protected DrawableComponent(Interfaces.ICanSupportComponents parent) :
            base(parent)
        {
            if (GameObjectsContent == null)
            {
                GameObjectsContent = new ResourceContentManager(parent.Services, Content.ResourceManager);
            }
            this.visible = true;
        }

        /// <summary>Releases all resources used by the <see cref="DrawableComponent"/>.</summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Allows the <see cref="DrawableComponent"/> to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.
        /// </summary>
        ~DrawableComponent()
        {
            Dispose(false);
        }

        /// <summary>
        /// Releases the unmanaged resources used by the <see cref="DrawableComponent"/> and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if ((disposing == true) && (this.graphicsDeviceService != null))
                {
                    unsubscribeFromGraphicsDeviceService();
                    this.graphicsDeviceService = null;
                }
                disposed = true;
            }
        }

        /// <summary>Gives the <see cref="DrawableComponent"/> a chance to initialize itself</summary>
        public override void Initialize()
        {

            // Look for the graphics device service in the game's service container
            this.graphicsDeviceService = GameServices.GetService(
              typeof(IGraphicsDeviceService)
            ) as IGraphicsDeviceService;

            // Like our XNA pendant, we absolutely require the graphics device service
            if (graphicsDeviceService == null)
                throw new InvalidOperationException("Graphics device service not found");

            // Done, now we can register to the graphics device service's events
            subscribeToGraphicsDeviceService();

        }

        /// <summary>Called before the <see cref="Draw"/> function. Here a component can draw on any render targets it wants.</summary>
        /// <param name="gameTime">Provides a snapshot of the game's timing values.</param>
        public virtual void DrawOnTargets(GameTime gameTime)
        {
        }

        /// <summary>Called when the <see cref="DrawableComponent"/> needs to draw itself.</summary>
        /// <param name="gameTime">Provides a snapshot of the game's timing values.</param>
        public virtual void Draw(GameTime gameTime)
        {
        }

        /// <summary>GraphicsDevice this <see cref="DrawableComponent"/> is bound to. Can be null.</summary>
        public GraphicsDevice GraphicsDevice
        {
            get { return this.graphicsDeviceService.GraphicsDevice; }
        }

        /// <summary>
        ///   Indicates when the <see cref="DrawableComponent"/> should be drawn in relation to other
        ///   drawables. Has no effect by itself.
        /// </summary>
        public int DrawOrder
        {
            get { return this.drawOrder; }
            set
            {
                if (value != this.drawOrder)
                {
                    this.drawOrder = value;
                    OnDrawOrderChanged();
                }
            }
        }

        /// <summary>True when the <see cref="DrawableComponent"/> is visible and should be drawn.</summary>
        public bool Visible
        {
            get { return this.visible; }
            set
            {
                if (value != this.visible)
                {
                    this.visible = value;
                    OnVisibleChanged();
                }
            }
        }

        /// <summary>Fires the DrawOrderChanged event</summary>
        protected virtual void OnDrawOrderChanged()
        {
            if (this.DrawOrderChanged != null)
            {
                this.DrawOrderChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>Fires the VisibleChanged event</summary>
        protected virtual void OnVisibleChanged()
        {
            if (this.VisibleChanged != null)
            {
                this.VisibleChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        ///   Called when graphics resources need to be loaded. Override this method to load
        ///   any game-specific graphics resources.
        /// </summary>
        protected virtual void LoadContent() { }


        /// <summary>
        ///   Called when graphics resources need to be unloaded. Override this method to unload
        ///   any game-specific graphics resources.
        /// </summary>
        protected virtual void UnloadContent() { }

        private void subscribeToGraphicsDeviceService()
        {
            this.graphicsDeviceService.DeviceCreated += new EventHandler<EventArgs>(deviceCreated);
            this.graphicsDeviceService.DeviceDisposing += new EventHandler<EventArgs>(deviceDisposing);

            if (this.graphicsDeviceService.GraphicsDevice != null)
            {
                LoadContent();
            }

        }

        private void unsubscribeFromGraphicsDeviceService()
        {
            this.graphicsDeviceService.DeviceCreated -= new EventHandler<EventArgs>(deviceCreated);
            this.graphicsDeviceService.DeviceDisposing -= new EventHandler<EventArgs>(deviceDisposing);
            if (this.graphicsDeviceService.GraphicsDevice != null)
            {
                UnloadContent();
            }

        }

        private void deviceCreated(object sender, EventArgs arguments)
        {
            LoadContent();
        }


        private void deviceDisposing(object sender, EventArgs arguments)
        {

            UnloadContent();
        }


        private SpriteBatchCollection _SBatches;

        /// <summary>
        /// Triggered when the value of the <see cref="GameObject.SBatches"/> property is changed.
        /// </summary>
        public event EventHandler<EventArgs> SpriteBatchSet;

        protected virtual void SignalSpriteBatch()
        {
            if ((SpriteBatchSet != null))
            {
                SpriteBatchSet(this, null);
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="SpriteBatch"/> used to draw this <see cref="GameObject"/>.
        /// </summary>
        public SpriteBatchCollection SBatches
        {
            get { return _SBatches; }
            set
            {
                SpriteBatchCollection old = _SBatches;
                _SBatches = value;
                if (_SBatches != old)
                    SignalSpriteBatch();
            }
        }




        private IGraphicsDeviceService graphicsDeviceService;
        private int drawOrder;
        private bool visible;
        private bool disposed = false;

        private static ContentManager goContent = null;

        internal ContentManager GameObjectsContent
        {
            get
            {
                return goContent;
            }
            private set
            {
                goContent = value;
            }
        }
    }
}
